//
//  APNS.swift
//  LocationTrackerAdmin
//
//  Created by mac on 2025/3/26.
//

import Vapor
import JWTKit

struct APNSConfiguration {
    let keyID: String
    let teamID: String
    let privateKey: String
    let bundleID: String
    let isSandbox: Bool
    
    var host: String {
        isSandbox ? "api.sandbox.push.apple.com" : "api.push.apple.com"
    }
}

struct APNSJWTGenerator {
    
    let configuration: APNSConfiguration
    
    func generateToken() async throws -> String {
        let payload = APNSPayload(
            iss: IssuerClaim(value: configuration.teamID),
            iat: IssuedAtClaim(value: Date())
        )
        let keys = JWTKeyCollection()
        try await keys.add(ecdsa: ES256PrivateKey(pem: configuration.privateKey), kid: JWKIdentifier(string: configuration.keyID))
        return try await keys.sign(payload, kid: JWKIdentifier(string: configuration.keyID))
    }
    
    private struct APNSPayload: JWTPayload {
        var iss: IssuerClaim  // 签发者 (Apple Team ID)
        var iat: IssuedAtClaim  // 签发时间
        
        func verify(using algorithm: some JWTAlgorithm) async throws {
            // 1. 验证签发时间 (iat) 不是未来时间
//            try iat.verifyNotInFuture()
//            
//            // 2. 验证令牌是否在有效期内 (APNS JWT 通常1小时有效)
//            let expirationTime = iat.value.addingTimeInterval(3600) // 1小时后过期
//            if Date() > expirationTime {
//                throw JWTError.claimVerificationFailure(name: "exp", reason: "Token expired")
//            }
//            
//            // 3. 验证签发者是否是预期的 Team ID
//            let expectedTeamID = "YOUR_TEAM_ID" // 应该从配置中获取
//            if iss.value != expectedTeamID {
//                throw JWTError.claimVerificationFailure(
//                    name: "iss",
//                    reason: "Issuer mismatch. Expected \(expectedTeamID), got \(iss.value)"
//                )
//            }
        }
    }
}

class PushJWTCache {
    static let shared = PushJWTCache()
    var jwt: String?
    var signDate: Date?
}

struct APNSClient {
    let app: Application
    let configuration: APNSConfiguration
    
    func sendBatch(tokens: [String], title: String, body: String, badge: Int? = nil, sound: String? = "default", mutableContent: Int? ,customData: [String: String]? = nil) {
        Task {
            for token in tokens {
                let _ = try await self.send(deviceToken: token, title: title, body: body, badge: badge, sound: sound, mutableContent: mutableContent, customData: customData)
            }
        }
    }
    
    func sendLocationPush(
        deviceToken: String,
        requester: String,
        badge: Int? = nil,
        sound: String? = "default",
        customData: [String: String]? = nil
    ) async throws -> ClientResponse {
        var jwt = ""
        let now = Date()
        if PushJWTCache.shared.jwt != nil && abs(now.timeIntervalSince(PushJWTCache.shared.signDate!)) < 20 * 60 {
            jwt = PushJWTCache.shared.jwt!
        } else {
            jwt = try await APNSJWTGenerator(configuration: configuration).generateToken()
            PushJWTCache.shared.jwt = jwt
            PushJWTCache.shared.signDate = Date()
        }//20分钟内使用缓存token，避免触发too many provider token错误
        let url = "https://\(configuration.host)/3/device/\(deviceToken)"
        struct PushPayload: Content {
            let requester: String
        }
        let res =  try await app.client.post(URI(string: url)) { req in
            req.headers = [
                "authorization": "bearer \(jwt)",
                "apns-topic": configuration.bundleID + ".location-query",
                "apns-priority": "10",
                "apns-push-type": "location"
            ]
            let payload = PushPayload(requester: requester)
            try req.content.encode(payload)
        }
        print("location push response is \(res)")
        return res
    }
    
    func send(
        deviceToken: String,
        title: String,
        body: String,
        badge: Int? = nil,
        sound: String? = "default",
        mutableContent: Int? = nil,
        customData: [String: String]? = nil
    ) async throws -> ClientResponse {
        var jwt = ""
        let now = Date()
        if PushJWTCache.shared.jwt != nil && abs(now.timeIntervalSince(PushJWTCache.shared.signDate!)) < 20 * 60 {
            jwt = PushJWTCache.shared.jwt!
        } else {
            jwt = try await APNSJWTGenerator(configuration: configuration).generateToken()
            PushJWTCache.shared.jwt = jwt
            PushJWTCache.shared.signDate = Date()
        }//20分钟内使用缓存token，避免触发too many provider token错误
        let url = "https://\(configuration.host)/3/device/\(deviceToken)"
        let payload = APNSPayload(
            aps: .init(
                alert: .init(title: title, body: body),
                badge: badge,
                sound: sound,
                mutableContent: mutableContent
            ),
            customData: customData
        )
        let response =  try await app.client.post(URI(string: url)) { req in
            req.headers = [
                "authorization": "bearer \(jwt)",
                "apns-topic": configuration.bundleID,
                "apns-priority": "10",
                "apns-push-type": "alert"
            ]
            try req.content.encode(payload)
        }
        print(response)
        return response
    }
}

// APNS 负载结构
struct APNSPayload: Content {
    struct APS: Content {
        struct Alert: Content {
            var title: String
            var body: String
        }
        
        var alert: Alert
        var badge: Int?
        var sound: String?
        var mutableContent: Int?
        enum CodingKeys: String, CodingKey {
            case alert, badge, sound
            case mutableContent = "mutable-content"
        }
    }
    
    var aps: APS
    var customData: [String: String]?
}

extension Application {
    struct APNSKey: StorageKey {
        typealias Value = APNSClient
    }
    
    var apns: APNSClient {
        get { storage[APNSKey.self]! }
        set { storage[APNSKey.self] = newValue }
    }
}
